#ifndef LUA_HPP
#define LUA_HPP

#include "../../Bind/Lua.hpp"

#ifdef BIND_HPP
	#include <Bind/Lua.hpp>
#else
	#include <lua.hpp>
#endif //Bind_HPP

// std Header
#include <string>

// Eigene Header
#include "Singleton.hpp"
#include "../Manager/ManagerTemplate.hpp"

namespace sl
{
	namespace tmp
	{
		class LuaS : public sl::Singleton<LuaS>
		{
			friend class sl::Singleton<LuaS>;

		public:
			LuaS() : myStatus(), myLua(NULL)
			{
				Open();
			}

			virtual ~LuaS()
			{
				Close();
			}

		private:
			lua_State* myLua;
			int myStatus;
			std::string myFile;

		public:
			lua_State* operator()()
			{
				return myLua;
			}

		public:
			LuaS* Open()
			{
				if (myLua == NULL)
				{
					sl::LogManager->Get("lua")->Write(sl::LogType::Info, "Lua started");

					myLua = lua_open();
					luaL_openlibs(myLua);
				}

				return this;
			}

			LuaS* Close()
			{
				if (myLua)
				{
					sl::LogManager->Get("lua")->Write(sl::LogType::Info, "Lua stopped");

					lua_close(myLua);
					myLua = NULL;
				}

				return this;
			}

			LuaS* GetError()
			{
				if (myStatus != 0)
				{
					int cnt = lua_gettop(myLua);
					while(cnt)
					{
						sl::LogManager->Get("error")->Write(sl::LogType::Error, lua_tostring(myLua, -cnt));
						sl::LogManager->Get("lua")->Write(sl::LogType::Error, lua_tostring(myLua, -cnt));
						cnt--;
					}
					lua_pop(myLua, 1);
				}

				return this;
			}

			LuaS* SetStatus(const int status)
			{
				myStatus = status;
				return this;
			}

			LuaS* SetFile(const std::string& filename)
			{
				myFile = filename;
				return this;
			}

			LuaS* Start()
			{
				if (myFile == "")
				{
					sl::LogManager->Get("error")->Write(sl::LogType::Error, "No file given");
					sl::LogManager->Get("lua")->Write(sl::LogType::Error, "No file given");
					return NULL;
				}

				return Start(myFile);
			}

			LuaS* Start(const std::string& file)
			{
				if (file == "")
					return this;

				//sl::LogManager->Get("lua")->Write(sl::LogType::Info, std::string("Try to run script \"") + file + std::string("\""));
				myStatus = luaL_dofile(myLua, file.c_str());

				if (myStatus != 0)
				{
					sl::LogManager->Get("error")->Write(sl::LogType::Error, "Can't run file \"" + file + "\"");
					sl::LogManager->Get("lua")->Write(sl::LogType::Error, "Can't run file \"" + file + "\"");

					GetError();
					return this;
				}

				return this;
			}

			LuaS* Execute(const std::string& string)
			{
				if (string == "")
					return this;

				sl::LogManager->Get("luafunction")->Write(sl::LogType::Info, "Start string: \n\"" + string + "\"\n");
				myStatus = luaL_loadstring(myLua, string.c_str());
				if (myStatus != 0)
				{
					sl::LogManager->Get("error")->Write(sl::LogType::Error, "Can't execute string: \n\"" + string + "\"\n");
					sl::LogManager->Get("lua")->Write(sl::LogType::Error, "Can't execute string: \n\"" + string + "\"\n");

					GetError();
					return this;
				}

				PCall(0, 0, 0);
				return this;
			}

			template <class T>
			LuaS* StartIfExists(T& t, const std::string& function)
			{
				lua_getfield(myLua, LUA_GLOBALSINDEX, "StartIfExists");
				lua_pushlightuserdata(lua, t);
				lua_pushstring(myLua, function.c_str());

				PCall(2, 0, 0);
				return this;
			}

			template <class T>
			LuaS* StartIfExists(T& t, const std::string& function, const std::string& args)
			{
				lua_getfield(lua, LUA_GLOBALSINDEX, "StartIfExists");
				lua_pushlightuserdata(myLua, t);
				lua_pushstring(myLua, function.c_str());

				int argc = 0;
				//TODO anpassen
				if (args != "")
				{
					argc = 1;
					int pos = 0;
					while (args.find(";"))
						pos = args.find(";");
						std::string arg = args.substr(pos, pos);
						if (arg != "")
						{
							lua_pushstring(myLua, arg.c_str());
						}
				}

				PCall(2 + argc, 0, 0);

				return this;
			}

			LuaS* Call(int nargs, int nresults)
			{
				return Call(myLua, nargs, nresults);
			}

			LuaS* Call(lua_State* lua, int nargs, int nresults)
			{
				lua_call(lua, nargs, nresults);
				return this;
			}

			LuaS* PCall(int nargs, int nresults, int errfunc = 0)
			{
				return PCall(myLua, nargs, nresults, errfunc);
			}

			LuaS* PCall(lua_State* lua, int nargs, int nresults, int errfunc = 0)
			{
				myStatus = lua_pcall(lua, nargs, nresults, errfunc);
				GetError();

				return this;
			}

			lua_State* GetState()
			{
				return myLua;
			}
		};
	}

	#define Lua tmp::LuaS::GetInstance()
}

#endif //LUA_HPP